package tool.codescan.visitors;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import tool.codescan.TypeResolver;
import tool.codescan.loggers.InterfaceAndClassLogger;
import tool.codescan.loggers.VariableAndParameterLogger;
import tool.data.TypeDefinition;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.type.ClassOrInterfaceType;
import japa.parser.ast.visitor.VoidVisitorAdapter;
import japa.parser.ast.CompilationUnit;

	/**
	 * @author jwb09119
	 * @param <A>
	 * @date 2013/11/11
	 * This class is a visitor class for interface oriented data gathering.
	 * 
	 * @date 2013/11/22
	 * This class will be the root of a set of recursive visitors.
	 */

public class JavaFileVisitor<A> extends VoidVisitorAdapter<A> implements IVisitorContext{

	private InterfaceAndClassLogger storage;
	private VariableAndParameterLogger useLogger;
	private TypeResolver typeResolver;
	
	// For this compilation unit
	private String packageName;
	private List<ImportDeclaration> imports;
	
	
	// Constructor
	public JavaFileVisitor (InterfaceAndClassLogger typeData, VariableAndParameterLogger useData,
										                               TypeResolver typeResolver) {
		
		storage = typeData;
		useLogger = useData;
		this.typeResolver = typeResolver;
		
	}
	
	
	@Override
	public void visit (ClassOrInterfaceDeclaration n, A arg) {
	    
		CompilationUnit containerCompliationUnit = (CompilationUnit) arg;
		
		// Get package
		PackageDeclaration pd = containerCompliationUnit.getPackage();
		packageName = pd.getName().toString();
		
		// Get imports
		imports = containerCompliationUnit.getImports();
		if (imports == null) {
		    imports = new ArrayList<>();
		}
		
		// Get interfaces
		List<TypeDefinition> interfaces = getAbsoluteNames(n.getImplements(), imports, packageName);
		
		// Get extended types
		List<TypeDefinition> extendsNames = getAbsoluteNames(n.getExtends(), imports, packageName);
		
        // Define the current type being looked at
        TypeDefinition currentVisitedType = new TypeDefinition(n.getName(), packageName);
        
		if(n.isInterface()) {
			
			storage.logInterface(currentVisitedType, interfaces, extendsNames);
			doInterfaceInternalsVisit(n, arg);
			
		} else if (Modifier.isAbstract(n.getModifiers())) {
			
			storage.logAbstractClass(currentVisitedType, interfaces, extendsNames);
			doAbstractClassInternalsVisit(n, arg);
			
		} else {
			
			// It's a concrete class
			storage.logClass(currentVisitedType, interfaces, extendsNames);
			doClassInternalsVisit(n, arg);
			
		}
		
	}


	/**
	 * @param types - a List of ClassOrInterfaceType, may be empty or NULL
	 * @param imports - a List of the imports for the containing class, may be empty or NULL
	 * @return A List of String representing unique identifiers for each interface, should not be
	 *         NULL
	 * 
	 * Constructs a package*(dot)name 'address' for an entity
	 * 
	 * NB - if interfaceTypes is empty or NULL, return will be an empty List of String.
	 * NB - if imports is empty or NULL, return will be an empty List of String.
	 */
	private List<TypeDefinition> getAbsoluteNames (List<ClassOrInterfaceType> types, 
	                                         List<ImportDeclaration> imports, String packageName) {

		List<TypeDefinition> reply = new ArrayList<>();
		
		if(imports == null) {
		    imports = new ArrayList<>();
		}
		
		if (types != null) {
			
			for (ClassOrInterfaceType type : types) {
			    
			    String name = TypeResolver.getTypeName(type);
				
				TypeDefinition address = typeResolver.groundTypeToSpecificImport (
				        new TypeDefinition(name), imports, packageName); 
				
				
				reply.add(address);
				
			}
			
		}
		
		return reply;
		
	}
	

	@Override
	public TypeDefinition getFullTypeAddress(String type) {
	    
	    TypeDefinition reply = null;
	    
	    // Primitive types are not imported 'from' anywhere
	    if(type.startsWith("PRIMITIVE.")) {
	        
	        reply = new TypeDefinition(type);
	        
	    } else if (reply == null) {
	        
	        reply = typeResolver.groundTypeToSpecificImport(new TypeDefinition(type), imports, packageName);
	        
	    }
	    
		return reply;
		
	}
	
	
	/******************
	 * RECURSIVE BITS *
	 ******************/	
	
	private void doClassInternalsVisit(ClassOrInterfaceDeclaration n, A arg) {

		ClassVisitor<A> ccVisitor = new ClassVisitor<A>(useLogger, this);
		n.accept(ccVisitor, arg);
		
	}


	private void doAbstractClassInternalsVisit(ClassOrInterfaceDeclaration n, A arg) {

		AbstractClassVisitor<A> acVisitor = new AbstractClassVisitor<A>(useLogger, this);
		n.accept(acVisitor, arg);
		
	}


	private void doInterfaceInternalsVisit(ClassOrInterfaceDeclaration n, A arg) {

		InterfaceVisitor<A> iVisitor = new InterfaceVisitor<A>(useLogger, this);
		n.accept(iVisitor, arg);
		
	}



	
}
